package cn.pink.core.gen.proxy;

import cn.pink.core.*;
import cn.pink.core.gen.GenBase;
import cn.pink.core.gen.GenUtils;
import cn.pink.core.gen.ImportOrganizer;
import cn.pink.core.gen.lock.Lock;
import cn.pink.core.support.*;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.Map.Entry;

/**
 * Proxy自动生成类
 * @author Pink
 */
public class GenProxy extends GenBase {
	
	private boolean hasGenericParamGlobal = false;
	
	public GenProxy(String packageName, String targetDir) throws Exception {
		super(packageName, targetDir);
		this.init();
	}
	
	/**
	 * 初始化class信息，判断能否生成字段
	 */
	private void init() throws Exception {
		this.canGen = true;
		
		// 遍历所有类信息，获取模版信息，判断是否出错，出错则初始化异常，不能生成
		Map<Class<?>, List<Method>> classes = GenUtils.findMethodNeedToGen(this.packageName, DistrClass.class, DistrMethod.class);
		for (Entry<Class<?>, List<Method>> entry : classes.entrySet()) {
			Class<?> clazz = entry.getKey();
			List<Method> methods = entry.getValue();
			try {
				this.rootMaps.add(getRootMap(clazz, methods));
			} catch(Exception e) {
				// 如果取模版内容异常，表示不能生成
				this.canGen = false;
				System.err.println("文件存在错误，不能继续进行Proxy生成了，错误如下：");
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 根据class及其methods获取填充模版的内容
	 */
	private Map<String, Object> getRootMap(Class<?> clazz, List<Method> methods) throws Exception {
		// 获取实体类名，表明，包名
		String packageName = clazz.getPackage().getName();
		String className = clazz.getSimpleName();
		
		// 获取DistrClass注解相关信息，port名，node名，servid，需要引入的包等
		Class<?>[] importPacks = (Class<?>[])GenUtils.getDistrClassField(clazz, "importClass");
		List<String> importPackages = new ArrayList<>();
		// 遍历获取包路径全名
		for (Class<?> pack : importPacks) {
			// 判断是内部类，则替换$
			String packName = pack.getName();
			if (pack.isMemberClass()) {
				packName = pack.getName().replace("$", ".");
			}
			
			importPackages.add(packName);
		}
		// 获取服务id
		Boolean singleton = (Boolean)GenUtils.getDistrClassField(clazz, "singleton");

		NodeType nodeType = (NodeType)GenUtils.getDistrClassField(clazz, "nodeType");

		
		Map<String, Object> rootMap = new HashMap<>();
		List<Map<String, Object>> methodInfos = new ArrayList<>();
		rootMap.put("rootPackageName", this.packageName);
		rootMap.put("packageName", packageName);
		rootMap.put("className", className);
		rootMap.put("methods", methodInfos);

		rootMap.put("methodFunctionAnnotation","@SuppressWarnings(\"unchecked\")");
		rootMap.put("singleton", singleton);
		rootMap.put("nodeType", nodeType);

		
		// 如果有引入的包，则设置
		if (!importPackages.isEmpty()) {
			rootMap.put("importPackages", 
					ImportOrganizer.newInstance()
					.appendPreConfiguredImports(Service.class.getName())
					.appendPreConfiguredImports(RPCImplBase.class.getName())
					.organizeImports(importPackages)
			);
		}
		
		boolean hasGenericParam = false;
		
		// 遍历methods，获取方法名，方法参数等信息
		for (Method m : methods) {
			// 模版所需数据
			String name = m.getName();
			String callerStr = packageName + "." + className + ":" + name;
			String paramsCall = "";
			String params = "";
			String functionTypes = "";
			boolean hasException = m.getExceptionTypes().length > 0;
			
			Map<String, Object> method = new LinkedHashMap<>();
			Map<String, Object> paramInfo = new LinkedHashMap<>();
			
			// 获取方法形参的类型，参数名
			Parameter[] paramList = m.getParameters();
			for (int i = 0; i < paramList.length; ++i) {
				/* 这边为了只保留class.getSimpleName()得到的类名，而不是类名$内部类名，用正则替换了$以前的字符为空
				 * 如com.google.protobuf.Message.Builder，Builder是Message的内部类，分别是如下结果
				 * ctclass.getSimpleName() -> Message$Builder
				 * ctclass.getSimpleName().replaceAll("^.+\\$", "") -> Builder
				 * 非内部类不影响结果
				 */
				// 参数类型
				String ptype = paramList[i].getType().getSimpleName();//.replaceAll("^.+\\$", "");
				// 是否是泛型参数
				if (paramList[i].getParameterizedType() instanceof ParameterizedType) {
					hasGenericParam = true;
					hasGenericParamGlobal = true;
				}
				// 参数名，非静态类第一个参数是this
				String pname = paramList[i].getName();
				paramInfo.put(pname, ptype);
			}
			
			//参数是不可变的
			DistrMethod mthAnn = (DistrMethod) m.getAnnotation(DistrMethod.class);

			boolean argsImmutable = mthAnn.argsImmutable();
			boolean isReturnAsync = mthAnn.isReturnAsync();

			//带不带锁
			Lock mthLock = m.getAnnotation(Lock.class);
			int timeout = mthLock == null ? -1 : mthLock.timeout();

			// 取出具体模版所需方法形参类型，形参名
			int j = 0;
			String callerStrTmp = "";
			
			for (Entry<String, Object> info: paramInfo.entrySet()) {
				String pname = info.getKey();
				String ptype = (String)info.getValue();
				
				if (j > 0) {
					params += ", ";
					callerStrTmp += ", ";
					paramsCall += ", ";
					functionTypes += ", ";
				}
				
				callerStrTmp += ptype;
				paramsCall += pname;
				// 因为：
				// (Object[]).getClass().getSimpleName = "Object[]"
				// (Object...).getClass().getSimpleName = "Object[]"
				// 通过反射获取到两者的类型都是数组
				// 为保证变参类型能够工作，故统一将类型名中的[]换成...
				// 这样变参类型一致了，但导致的缺陷是原本的数组类型不一致了，缺陷案例如下
				// func(Long[] args, Long arg)会生成：
				// func(Long...args, Long arg)，而该函数编译不通过
				// 结论：变参必须是最后一个参数，数组会被转换成变参，故数组也必须是最后一个参数
				params += ptype.replaceAll("\\[\\]", "...") + " " + pname;
				functionTypes += GenUtils.primitiveTowrapper(ptype);
//				if (ptype.equals("List") || ptype.equals("Map") || ptype.equals("Set")) {
//					hasGenericParam = true;
//				}
				j ++;
			}
			
			callerStr += "(" + callerStrTmp + ")";
			
			if (StringUtils.isNotBlank(functionTypes)) {
				functionTypes = "<" + functionTypes + ">";
			}
			
			method.put("name", name);
			method.put("params", params);
			method.put("hasException", hasException);
			method.put("callerStr", callerStr);
			method.put("paramsCall", paramsCall);
			method.put("functionTypes", functionTypes);
			method.put("paramsSize", j);
			method.put("argsImmutable", argsImmutable);
			method.put("lockTimeout", timeout);
			method.put("isReturnAsync", isReturnAsync);

			//生成方法名对应的Enum常量
			String enumCall = callerStr.replace("()", "").replace("[]", "s")
					.replaceAll("[.:(,]", "_").replaceAll("[ )]", "").toUpperCase();
			method.put("enumCall", enumCall);
			method.put("enumCallHashCode", String.valueOf(enumCall.hashCode()));
			
			methodInfos.add(method);
		}
		
		if (hasGenericParam) {
			// 泛型的参数列表不会写到模板里，因此加上rawtypes
			rootMap.put("mainAnnotation","@SuppressWarnings({ \"unchecked\", \"rawtypes\" })");
		} else {
			rootMap.put("mainAnnotation","@SuppressWarnings(\"unchecked\")");
		}
		
		return rootMap;
	}

	@Override
	protected void genFileHandler(Map<String, Object> rootMap) throws Exception {
		rootMap.put("rootPackageName", this.packageName);
		String fileName = (String)rootMap.get("className") + "Impl.java";
		String packName = (String)rootMap.get("packageName");
		String targetFileDir = this.targetDir + ClassFinder.packageToPath(packName) + "/";
		
		Utils.freeMarker(TEMP_DIR, "RPCImpl.ftl", rootMap, targetFileDir, fileName);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected void genGlobalFileHandler(Map<String, Object> rootMaps) throws Exception {
		rootMaps.put("packageName", this.packageName);

		boolean singleton = false;
		NodeType nodeType = NodeType.NONE;
		Set<String> importPackages = new LinkedHashSet<>();
		for (Map<String, Object> proxyMap : this.rootMaps) {
			List<String> proxyImportPackages = (List<String>)proxyMap.get("importPackages");
			if (proxyImportPackages != null && !proxyImportPackages.isEmpty()) {
				importPackages.addAll(proxyImportPackages);
			}
			singleton = singleton || (Boolean)proxyMap.get("singleton");
			nodeType = (NodeType) proxyMap.get("nodeType");
		}
		
		if (!importPackages.isEmpty()) {
			rootMaps.put("importPackages", 					
					ImportOrganizer.newInstance()
					.appendPreConfiguredImports(RPCProxyBase.class.getName())
					.appendPreConfiguredImports(CallPoint.class.getName())
					.appendPreConfiguredImports(Port.class.getName())
					.appendPreConfiguredImports(Param.class.getName())
					.appendPreConfiguredImports(Utils.class.getName())
					.appendPreConfiguredImports(Log.class.getName())
					.appendPreConfiguredImports(Call.class.getName())
					.organizeImports(importPackages)
			);
		}
		
		rootMaps.put("singleton", singleton);
		rootMaps.put("nodeType", nodeType);

		if (this.hasGenericParamGlobal) {
			// 泛型的参数列表不会写到模板里，因此加上rawtypes
			rootMaps.put("mainAnnotation","@SuppressWarnings(\"rawtypes\")");
		} else {
			rootMaps.put("mainAnnotation","");
		}
		
		String targetFileDir = System.getProperty("user.dir") + "/common/src/gen/java/" + ClassFinder.packageToPath(this.packageName) + "/";
		Utils.freeMarker(TEMP_DIR, "RPCProxy.ftl", rootMaps, targetFileDir, "RPCProxy.java");
	}
	
	public static void main(String[] args) throws Exception {
		if (args.length < 2) {
			System.err.println("Usage GenProxy packName targetDir");
			return;
		}
		
		// 设置log4j日志文件名
		System.setProperty("logFileName", "GenProxy");
		
		String packName = args[0];
		String targetDir = System.getProperty("user.dir") + args[1];
		
		GenProxy genProxy = new GenProxy(packName, targetDir);
		if (!genProxy.isCanGen()) {
			System.err.println("不能生成Proxy");
			return;
		}

		genProxy.genFiles();
	}

}
